package seatiger.tomcat.coyote.http11;

import seatiger.tomcat.catalina.Constants;
import seatiger.tomcat.coyote.*;
import seatiger.tomcat.coyote.http11.filters.*;
import seatiger.tomcat.util.buf.MessageBytes;
import seatiger.tomcat.util.net.NioChannel;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.util.Date;
import java.util.Map;

/**
 * 使用nio 处理http报文
 */
public class Http11NioProcessor implements ActionHook {

    protected NioChannel nioChannel;
    protected InternalNioInputBuffer inputBuffer;
    protected InternalNioOutputBuffer outputBuffer;
    protected Request request;

    protected Response response;

    protected Adapter adapter = null;

    public Http11NioProcessor(int maxHttpHeaderSize) {
        request = new Request();
        this.inputBuffer = new InternalNioInputBuffer(request,maxHttpHeaderSize);
        request.setInputBuffer(inputBuffer);
        response = new Response();
        response.setHook(this);
        outputBuffer = new InternalNioOutputBuffer(response, maxHttpHeaderSize);
        response.setOutputBuffer(outputBuffer);
        initializeFilters();
    }

    public void setAdapter(Adapter adapter) {
        this.adapter = adapter;
    }

    /**
     * 初始化过滤器
     */
    protected void initializeFilters() {
        //添加读取过滤器
        inputBuffer.addFilter(new IdentityInputFilter());
        inputBuffer.addFilter(new VoidInputFilter());

        //添加输出过滤器
        outputBuffer.addFilter(new IdentityOutputFilter());
        outputBuffer.addFilter(new VoidOutputFilter());
        outputBuffer.addFilter(new ChunkedOutputFilter());
    }

    /**
     * 如果一个请求报文 触发了多次读事件
     * @param nioChannel
     * @throws IOException
     */
    public void process(NioChannel nioChannel) throws IOException {
        this.nioChannel = nioChannel;
        this.inputBuffer.setNioChannel(nioChannel);
        this.outputBuffer.setNioChannel(nioChannel);

        /**
         * 因为这是nio模式，读到数据 说明有数据过来，就需要解析
         * 如果没有数据，就要退出循环，释放资源，重新关注读事件，等下读事件发生
         */
        while (true) {
            if (!inputBuffer.parseRequestLine()) {
                //找到注册在上 Selector 上面的selectionKey
                //重新关注上 SelectionKey.OP_READ 事件
                SelectionKey selectionKey =
                        nioChannel.getIOChannel().keyFor(nioChannel.getPoller().getSelector());
                selectionKey.interestOps(SelectionKey.OP_READ);
                break;
            }
            //TODO if(!inputBuffer.parseHeaders()) {....}
            inputBuffer.parseHeaders();
            prepareRequest();
            try {
                //TODO
                adapter.service(request, response);
                inputBuffer.endRequest();
                //看一下有没有下一个请求的报文到达，同时清理一些状态
                inputBuffer.nextRequest();
                outputBuffer.nextRequest();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
        /**
         * 这里的重置需要 考虑其他情况
         *
         * 1. 如果一个报文触发了多次读事件，那么就不能简单的直接重置，因为第二次的读事件，还需要保留之前的一些状态
         */
        recycle();
    }

    /**
     *
     */
    public void recycle() {
        inputBuffer.recycle();
        outputBuffer.recycle();
        this.nioChannel = null;
    }

    protected void prepareRequest() {
        InputFilter[] inputFilters = inputBuffer.getFilters();
        int contentLength = request.getContentLength();
        if (contentLength >= 0) {
            inputBuffer.addActiveFilter(inputFilters[Constants.IDENTITY_FILTER]);
        } else {
            inputBuffer.addActiveFilter(inputFilters[Constants.VOID_FILTER]);
        }
    }


    @Override
    public void action(ActionCode actionCode, Object param) {
        //提交，第一次写出数据，一般写出报文头
        if (actionCode == ActionCode.ACTION_COMMIT) {
            if (response.isCommited()) {
                return;
            }
            //验证一些头部信息，来决定使用哪个fiter
            prepareResponse();
            try {
                outputBuffer.commit();
            }catch (IOException e){
            }
        }else if (actionCode == ActionCode.ACTION_CLOSE) {
            //数据已经刷出了，做一些收尾的动作
            try {
                outputBuffer.endRequest();
            }catch (Exception e) {

            }
        }
    }

    protected void prepareResponse() {
        OutputFilter[] outputFilters = outputBuffer.getFilters();
        int statusCode = response.getStatus();
        if (statusCode == 204 || statusCode == 205 || statusCode == 304) {
            outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
        }
        MessageBytes method = request.method();
        if (method.toString().equals("HEAD")) {
            outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
        }

        int contentLength = response.getContentLength();
        if (contentLength >= 0) {
            outputBuffer.addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
            response.addHeader("Content-Length",String.valueOf(contentLength));
        } else {
            //使用chunked
            outputBuffer.addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]);
            response.addHeader("Transfer-Encoding","chunked");
        }
        response.addHeader("Date",new Date().toString());
        response.addHeader("Server","my tomcat");
        outputBuffer.sendStatus();
        Map<String, String> headers = response.getHeaders();
        headers.keySet().forEach(key -> outputBuffer.sendHeader(key,headers.get(key)));
        outputBuffer.endHeaders();
        //TODO 使用chuncked
    }


}
